#!/bin/bash

# cjgit - Cangjie Git Manager - Common Functions
# Shared functions and configuration for all cjgit commands

# Configuration
PROJECTS=("cangjie_compiler" "cangjie_runtime" "cangjie_stdx" "cangjie_tools" "cangjie_multiplatform_interop" "cangjie_docs")
UPSTREAM_BASE="https://gitcode.com/Cangjie"
SSH_BASE="git@gitcode.com"

# Colors and formatting
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
GRAY='\033[0;90m'
BOLD='\033[1m'
NC='\033[0m' # No Color

# Function to print colored output
print_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# Function to find the correct remote name
find_cangjie_remote() {
    local dir="$1"
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        return 1
    fi
    
    # Use git -C to run git commands without changing directory
    git -C "$dir" remote -v | while read remote url type; do
        if [[ "$url" == *"gitcode.com:Cangjie"* ]] || [[ "$url" == *"gitcode.com/Cangjie"* ]]; then
            echo "$remote"
            return 0
        fi
    done
    
    return 1
}

# Function to check if there are local changes to tracked files
has_local_changes() {
    local dir="$1"
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        return 1
    fi
    
    # Check only for changes to tracked files (ignore untracked files)
    # git diff-index checks working tree against index, --quiet suppresses output
    if ! git -C "$dir" diff-index --quiet HEAD --; then
        return 0  # Has local changes to tracked files
    else
        return 1  # No local changes to tracked files
    fi
}

# Function to check if upstream has changes (for [pull] badge)
upstream_has_changes() {
    local dir="$1"
    local remote="$2"
    
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        return 1
    fi
    
    # Fetch from remote
    if ! git -C "$dir" fetch "$remote" >/dev/null 2>&1; then
        return 1
    fi
    
    # Check if local branch is behind remote using git rev-list (locale-independent)
    local local_commit remote_commit
    local_commit=$(git -C "$dir" rev-parse HEAD)
    remote_commit=$(git -C "$dir" rev-parse "$remote/dev" 2>/dev/null)
    
    if [[ $? -ne 0 ]]; then
        # Remote branch doesn't exist
        return 1
    fi
    
    # Check if remote has commits that we don't have
    # This handles both cases: when we're behind AND when branches have diverged
    if ! git -C "$dir" merge-base --is-ancestor "$remote_commit" "$local_commit" 2>/dev/null; then
        # If remote_commit is NOT an ancestor of local_commit, then remote has commits we don't have
        return 0  # Remote has changes we can pull
    fi
    
    return 1  # Up to date or ahead
}

# Function to check if has changes to push to myfork (for [push] badge)
has_changes_to_push() {
    local dir="$1"
    
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        return 1
    fi
    
    # Check if myfork remote exists
    if ! git -C "$dir" remote get-url myfork >/dev/null 2>&1; then
        return 1
    fi
    
    # Get current branch
    local current_branch
    current_branch=$(git -C "$dir" branch --show-current)
    
    if [[ -z "$current_branch" ]]; then
        return 1
    fi
    
    # Check if we're ahead of myfork (only checks committed changes, ignores uncommitted)
    if git -C "$dir" status | grep -q "Your branch is ahead of"; then
        return 0
    fi
    
    # Alternative check using rev-list (more reliable)
    local local_commit remote_commit
    local_commit=$(git -C "$dir" rev-parse HEAD)
    remote_commit=$(git -C "$dir" rev-parse "myfork/$current_branch" 2>/dev/null)
    
    if [[ $? -eq 0 ]]; then
        if git -C "$dir" merge-base --is-ancestor "$remote_commit" "$local_commit" && [[ "$local_commit" != "$remote_commit" ]]; then
            return 0  # Ahead of myfork
        fi
    fi
    
    return 1
}

# Function to check if there are conflicts with myfork (for red [myfork] badge)
has_conflicts_with_myfork() {
    local dir="$1"
    
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        return 1
    fi
    
    # Check if myfork remote exists
    if ! git -C "$dir" remote get-url myfork >/dev/null 2>&1; then
        return 1
    fi
    
    # Get current branch
    local current_branch
    current_branch=$(git -C "$dir" branch --show-current)
    
    if [[ -z "$current_branch" ]]; then
        return 1
    fi
    
    # Fetch from myfork to get latest state
    if ! git -C "$dir" fetch myfork >/dev/null 2>&1; then
        return 1
    fi
    
    # Check if local branch is behind myfork (conflict situation)
    local local_commit remote_commit
    local_commit=$(git -C "$dir" rev-parse HEAD)
    remote_commit=$(git -C "$dir" rev-parse "myfork/$current_branch" 2>/dev/null)
    
    if [[ $? -eq 0 ]]; then
        # If remote_commit is NOT an ancestor of local_commit, then myfork has commits we don't have
        if ! git -C "$dir" merge-base --is-ancestor "$remote_commit" "$local_commit" 2>/dev/null; then
            return 0  # Behind myfork (potential conflicts)
        fi
    fi
    
    return 1  # Up to date or ahead
}

# Function to get current branch name
get_current_branch() {
    local dir="$1"
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        echo ""
        return 1
    fi
    
    git -C "$dir" branch --show-current
}

# Function to get space-separated remote names
get_remote_names() {
    local dir="$1"
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        echo ""
        return 1
    fi
    
    git -C "$dir" remote | tr '\n' ' ' | sed 's/ $//'
}

# Function to check if myfork remote exists
has_myfork_remote() {
    local dir="$1"
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        return 1
    fi
    
    git -C "$dir" remote get-url myfork >/dev/null 2>&1
}

# Function to get push remote (myfork if exists, otherwise default)
get_push_remote() {
    local dir="$1"
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        echo ""
        return 1
    fi
    
    if has_myfork_remote "$dir"; then
        echo "myfork"
    else
        # Try to get the default push remote
        local current_branch
        current_branch=$(get_current_branch "$dir")
        if [[ -n "$current_branch" ]]; then
            local push_remote
            push_remote=$(git -C "$dir" config "branch.$current_branch.remote")
            if [[ -n "$push_remote" ]]; then
                echo "$push_remote"
            else
                echo ""
            fi
        else
            echo ""
        fi
    fi
}

# Function to get commits ahead of upstream/dev
get_commits_ahead_of_upstream_dev() {
    local dir="$1"
    local upstream_remote="$2"
    
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        echo ""
        return 1
    fi
    
    # Fetch the latest from upstream
    git -C "$dir" fetch "$upstream_remote" >/dev/null 2>&1
    
    # Get commits that are in current branch but not in upstream/dev
    git -C "$dir" log --oneline "$upstream_remote/dev..HEAD" 2>/dev/null
}

# Function to get full commit message (including body) for a specific commit
get_full_commit_message() {
    local dir="$1"
    local commit_hash="$2"
    
    if [[ ! -d "$dir" ]] || [[ ! -d "$dir/.git" ]]; then
        echo ""
        return 1
    fi
    
    # Get the full commit message
    git -C "$dir" log --format=%B -n 1 "$commit_hash" 2>/dev/null
}

# Function to validate commit message summary line
validate_commit_message_summary() {
    local message="$1"
    
    # Check if message starts with "fix: " or "feature: "
    if [[ ! "$message" =~ ^(fix:|feature:)\ .* ]]; then
        print_error "Commit message must start with 'fix: ' or 'feature: '"
        return 1
    fi
    
    # Check there's no dot at the end
    if [[ "$message" =~ \.$ ]]; then
        print_error "Commit message should not end with a dot"
        return 1
    fi
    
    # Extract the part after the colon and spaces
    local after_colon
    after_colon=$(echo "$message" | sed -E 's/^(fix:|feature:) +//')
    
    # Check if the first character after colon is uppercase
    if [[ "$after_colon" =~ ^[A-Z] ]]; then
        print_error "Commit message after colon should not start with capital letter"
        return 1
    fi
    
    return 0
}

# Function to validate full commit message (including signed-off field)
validate_commit_message() {
    local full_message="$1"
    
    # Extract the first line (summary)
    local summary_line
    summary_line=$(echo "$full_message" | head -1)
    
    # Validate the summary line
    if ! validate_commit_message_summary "$summary_line"; then
        return 1
    fi
    
    # Check for signed-off field in the commit message
    if ! echo "$full_message" | grep -q "^Signed-off-by:"; then
        print_error "Commit message must contain a Signed-off-by field"
        return 1
    fi
    
    return 0
}

# Function to read GitCode configuration from config file
read_gitcode_config() {
    local config_file="$HOME/.config/cjpr.conf"
    
    # Initialize variables
    GITCODE_USER=""
    TOKEN=""
    TOKEN_STORED=""
    
    # Check if config file exists
    if [[ ! -f "$config_file" ]]; then
        return 1
    fi
    
    # Read the config file line by line
    while IFS= read -r line; do
        # Skip empty lines and comments
        [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
        
        # Parse GITCODE_USER
        if [[ "$line" =~ ^GITCODE_USER= ]]; then
            GITCODE_USER="${line#GITCODE_USER=}"
        fi
        
        # Parse TOKEN
        if [[ "$line" =~ ^TOKEN= ]]; then
            TOKEN="${line#TOKEN=}"
        fi
        
        # Parse TOKEN_STORED
        if [[ "$line" =~ ^TOKEN_STORED= ]]; then
            TOKEN_STORED="${line#TOKEN_STORED=}"
        fi
    done < "$config_file"
    
    # If TOKEN is missing but TOKEN_STORED is true, read from pass utility
    if [[ -z "$TOKEN" && "$TOKEN_STORED" == "true" && -n "$GITCODE_USER" ]]; then
        if command -v pass >/dev/null 2>&1; then
            TOKEN=$(pass show "gitcode.com/$GITCODE_USER" 2>/dev/null)
            if [[ $? -ne 0 ]]; then
                print_error "Failed to read token from pass utility for gitcode.com/$GITCODE_USER"
                return 1
            fi
        else
            print_error "pass utility not found, but TOKEN_STORED=true"
            return 1
        fi
    fi
    
    # Check if GITCODE_USER is set and non-empty
    if [[ -n "$GITCODE_USER" ]]; then
        return 0
    else
        return 1
    fi
}